package org.pitest.ant;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A {@see Mutators} instance contains the configuration information for all of the pitest mutators.
 * <p/>
 * This allows the user to override the default settings for the mutators used by pitest. To override
 * a mutator setting a new {@see Mutator} instance is created. The values in the {@see Mutator} instances
 * take precidence over the default settings.
 *
 * @author Aidan Morgan
 */
public class Mutators {
  /**
   * A map of mutator name to enabled state that mirrors the default mutators that are defined
   * on the pitest website.
   * <p/>
   * These values are used by default, unless the task definition overrides them somewhere.
   */
  private static Map<String, Boolean> DEFAULT_MUTATORS = new HashMap<String, Boolean>();

  // initialise the DEFAULT_MUTATORS map with the default values.
  static {
    DEFAULT_MUTATORS.put("CONDITIONALS_BOUNDARY", Boolean.TRUE);
    DEFAULT_MUTATORS.put("NEGATE_CONDITIONALS", Boolean.TRUE);
    DEFAULT_MUTATORS.put("MATH", Boolean.TRUE);
    DEFAULT_MUTATORS.put("INCREMENTS", Boolean.TRUE);
    DEFAULT_MUTATORS.put("INVERT_NEGS", Boolean.TRUE);
    DEFAULT_MUTATORS.put("INLINE_CONSTS", Boolean.TRUE);
    DEFAULT_MUTATORS.put("RETURN_VALS", Boolean.TRUE);
    DEFAULT_MUTATORS.put("VOID_METHOD_CALL", Boolean.TRUE);
    DEFAULT_MUTATORS.put("NON_VOID_METHOD_CALL", Boolean.FALSE);
    DEFAULT_MUTATORS.put("CONSTRUCTOR_CALL", Boolean.FALSE);
    DEFAULT_MUTATORS.put("EXPERIMENTAL_INLINE_CONSTS", Boolean.FALSE);
  }

  /**
   * A {@see List} of addititional configuration of mutators. Values in this list take precendence over
   * the default values.
   */
  private List<Mutator> mutators;

  /**
   * The {@see PitestAntTask} that owns this instance.
   */
  private PitestAntTask task;

  /**
   * Constructor.
   *
   * @param task the {@see PitestAntTask} instance that owns this instance.
   */
  public Mutators(PitestAntTask task) {
    this.task = task;
    this.mutators = new ArrayList<Mutator>();
  }

  /**
   * Creates a new {@see Mutator} instance, allowing the task to override the default values for mutators,
   * or introduce new mutators.
   *
   * @return a new {@see Mutator} instance to be configured by the task.
   */
  public Mutator createMutator() {
    Mutator m = new Mutator(this);
    mutators.add(m);

    return m;
  }

  /**
   * Returns the mutator configuration as a string that can be provided to the command-line of pitest.
   *
   * @param seperator the seperator to use between mutator names.
   * @return a string representing the mutator configuration in the format the command-line of pitest is expecting.
   */
  public String asString(String seperator) {
    // take a copy of the default settings
    Map<String, Boolean> vals = new HashMap<String, Boolean>(DEFAULT_MUTATORS);

    // iterate over any user-defined settings, allowing the default to be overridden if needed.
    // This has the added benefit of allowing additional mutators to be defined that aren't
    // known about when this class is compiled!
    for (Mutator m : mutators) {
      vals.put(m.getName(), m.isEnabled());
    }

    // now build a string representation of all of the enabled mutators only.
    StringBuffer buff = new StringBuffer();
    String sep = "";

    for (Map.Entry<String, Boolean> entry : vals.entrySet()) {
      // if it's set to false then don't bother including it in the list of mutators to use.
      if (entry.getValue()) {
        buff.append(sep);
        buff.append(entry.getKey());

        sep = seperator;
      }
    }

    return buff.toString();
  }

  /**
   * A {@see Mutator} represents a specific defined configuration of a pitest mutation that is not
   * the default configuration.
   */
  public class Mutator {
    /**
     * The name of the mutation to be configured.
     */
    private String name;

    /**
     * Whether the mutation should be enabled or not.
     */
    private boolean enabled;

    /**
     * The {@see Mutators} instance that owns this {@see Mutator}.
     */
    private Mutators mutators;

    /**
     * Constructor.
     *
     * @param m the {@see Mutators} instance that owns this {@see Mutator}.
     */
    public Mutator(Mutators m) {
      this.mutators = m;
    }

    /**
     * Returns the name of the mutator this {@see Mutator} instance is configuring.
     *
     * @return the name of the mutator this {@see Mutator} instance is configuring.
     */
    public String getName() {
      return name;
    }

    /**
     * Sets the name of the mutator that this {@see Mutator} instance is configuring.
     *
     * @param name the name of the mutator to be configured.
     * @throws IllegalArgumentException if the provided mutator name is null.
     */
    public void setName(String name) {
      if (name == null) {
        throw new IllegalArgumentException("Cannot specify a null name for a mutator.");
      }

      this.name = name;
    }

    /**
     * Returns {@code true} if the mutator should be enabled, {@code false} otherwise.
     *
     * @return {@code true} if the mutator should be enabled, {@code false} otherwise.
     */
    public boolean isEnabled() {
      return enabled;
    }

    /**
     * Sets whether the mutator should be enabled ({@code true} or disabled ({@code false}.
     *
     * @param enabled whether the mutator should be enabled ({@code true} or disabled ({@code false}.
     */
    public void setEnabled(boolean enabled) {
      this.enabled = enabled;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Mutator mutator = (Mutator) o;

      if (enabled != mutator.enabled) return false;
      if (!name.equals(mutator.name)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = name.hashCode();
      result = 31 * result + (enabled ? 1 : 0);
      return result;
    }
  }
}
